BPF-eBPF 开发路线二:libbpf、CO-RE 与 libbpf-bootstrap 实战
一个人知道自己为什么而活,就可以忍受任何一种生活。——尼采
写在前面
- 如果说
BCC更偏“先跑起来”,那libbpf更偏“把 eBPF 真正学扎实” - 这一篇会尽量贴近
libbpf-bootstrap与libbpf官方文档的学习脉络 - 重点放在
CO-RE、BTF、对象装载、示例项目阅读顺序和最小改造方法
一个人知道自己为什么而活,就可以忍受任何一种生活。——尼采
系列导航
BPF-eBPF 学习总览:从概念、机制到工具链选择BPF-eBPF 实战入门:环境准备、最小实验与排错思路BPF-eBPF 开发路线一:BCC 入门、工具使用与自定义脚本BPF-eBPF 开发路线二:libbpf、CO-RE 与 libbpf-bootstrap 实战BPF-eBPF 开发路线三:ebpf-go、bpf2go 与 Go 工程集成
为什么说 libbpf 是主干路线
现在很多生产级 eBPF 程序,本质上都绕不开 libbpf 这一层。
原因很简单:
- 它贴近内核原生生态
CO-RE支持成熟- 对对象加载、attach、map 管理、link 生命周期都有比较标准的组织方式
- 后续无论你走 C 路线还是 Go 路线,理解
libbpf都会让你更清楚底层到底发生了什么
开始学 libbpf,先看什么
如果你直接啃 libbpf 全量文档,很多初学者会被细节压垮。更稳妥的顺序是:
- 先看
libbpf-bootstrap - 先跑
examples/c/minimal - 再跑
examples/c/bootstrap - 再去看
kprobe、uprobe、fentry、usdt - 最后结合
libbpf.readthedocs.io查概念和 API
也就是说,不建议一开始就“API 背诵式学习”,而建议先从一个能运行的标准工程骨架切入。
libbpf-bootstrap 为什么适合入门
https://github.com/libbpf/libbpf-bootstrap 这个项目最有价值的地方在于,它不是只给你一段零散代码,而是给你一整套更接近真实项目的组织方式。
它通常会让你接触到这些关键点:
.bpf.c内核态程序文件- 用户态加载程序
- skeleton 生成
ring buffer事件传递vmlinux.hCO-RE
对初学者来说,这比“只会 attach 一个 probe”更完整。
建议先跑哪些示例
1. minimal
这是最适合理解“对象最小形态”的例子。
重点看:
- eBPF 程序怎么写在
.bpf.c - 用户态怎么 open/load/attach
- 一个最小 skeleton 是怎么参与构建的
2. bootstrap
这是最值得精读的示例之一。
重点看:
- 命令行参数
- ring buffer 事件读取
- 结构体数据怎么从内核态传给用户态
- 程序如何优雅退出
3. kprobe / uprobe
这两个例子最适合帮助你建立“内核态函数”和“用户态函数”被观测时的差异感。
4. xdp / tc
如果你明确要走网络方向,再往这两个例子深入会更有价值。
一次典型的 libbpf 开发流程
你可以把它概括成下面几步:
- 编写
.bpf.c - 通过
clang编译出 eBPF 对象文件 - 生成 skeleton
- 用户态程序加载对象
- attach 到 hook
- 读取 map 或 ring buffer 数据
这套流程里,最关键的理解点是:
.bpf.c不等于整个程序- 真正交付的是“内核态对象 + 用户态加载器”的组合
- 用户态要负责对象生命周期、attach 生命周期和数据读取
CO-RE 到底解决了什么问题
CO-RE 本质上是为了解决“内核结构体和布局会变”的问题。
如果没有它,你可能会遇到这些情况:
- 本机能跑,换内核就挂
- 结构体偏移变了,程序读错字段
- 需要为不同发行版维护多份对象文件
而 CO-RE 的基本思想是:
- 编译时保留足够的类型重定位信息
- 运行时根据目标内核的
BTF去调整访问
所以学 libbpf 时,CO-RE 不是“进阶选修”,而几乎是现代 eBPF 开发的基础配置。
vmlinux.h 在这里扮演什么角色
很多初学者看到 vmlinux.h 会以为这是某种普通头文件,其实它更像是:
- 当前目标内核类型信息的 C 头表示
- 让你的 eBPF 程序能够引用内核结构体和字段
常见生成方式:
1 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h |
注意这里也再次说明了一个现实:
- 现代 eBPF 开发已经越来越依赖
BTF - 不理解
vmlinux.h和BTF,后面会很难继续走深
一个很实用的学习动作:改 bootstrap 示例
不要停留在“我把示例跑起来了”,建议至少做一次小改动。
比如:
- 给事件结构体多加一个字段
- 在
.bpf.c里填充这个字段 - 在用户态打印出来
这个小动作能强迫你真正理解:
- 内核态和用户态如何共享结构体定义
- ring buffer 里到底传了什么
- 修改后为什么需要重新编译对象和 skeleton
学 libbpf 时一定要理解的几个术语
object
表示一组已加载或待加载的 BPF 程序与 map 的集合。
skeleton
是围绕 BPF 对象自动生成的辅助封装,目的是降低样板代码量,让 open/load/attach 等流程更标准。
link
表示 attach 之后形成的连接对象。很多现代 attach 流程都会强调 link 生命周期管理。
ring buffer
常见于事件流场景,用来把内核态事件高效送到用户态。
libbpf 这条路线容易踩的坑
1. 以为“编译成功”就等于“能跑”
真正的问题常常发生在:
- load
- attach
- verifier
- 运行时字段访问
2. 不理解 section 命名
不同 SEC("...") 对应不同 program type 和 attach 语义,不能随便写。
3. 不重视 BTF/BTF 缺失
很多问题最后都追溯到:
- 目标机没有可用 BTF
vmlinux.h不匹配- 没有按
CO-RE的方式组织对象
4. 只会抄示例,不会做最小改动
如果你跑通示例后完全不敢动它,那说明你还没真正进入可开发状态。
我建议的 libbpf 学习节奏
- 跑
minimal - 跑
bootstrap - 精读
bootstrap - 改一个事件字段
- 再读
libbpf文档中和 map、program、link、CO-RE 相关的部分 - 最后才去补更底层的 API 细节
这条路径通常比“从 API 文档第一页开始通读”更高效。
最后总结
如果你想真正掌握 eBPF,libbpf 几乎是绕不过去的一步。
因为它逼着你直面这些核心问题:
- eBPF 对象是怎么组织的
- 为什么现代开发强调
CO-RE - 内核态和用户态到底如何配合
- 程序 attach 之后生命周期怎么管理
把这几个问题吃透之后,你对 eBPF 的理解会从“会跑 demo”进入“能写应用”的阶段。
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
BPF-eBPF 开发路线二:libbpf、CO-RE 与 libbpf-bootstrap 实战
1.BPF-eBPF 开发路线三:ebpf-go、bpf2go 与 Go 工程集成
2.BPF-eBPF 开发路线一:BCC 入门、工具使用与自定义脚本
3.BPF-eBPF 实战入门:环境准备、最小实验与排错思路
4.BPF-eBPF 学习总览:从概念、机制到工具链选择
5.Linux性能调优之使用BPF工具观测CPU性能指标
6.认识 Linux 内存构成:Linux 内存调优之内存分配机制和换页行为认知
7.BPF:BCC工具 funccount 统计内核函数调用(内核函数、跟踪点USDT探针)认知
8.BPF:BCC(BPF Compiler Collection)工具集认知

